package util;

public class Base64Util {
	
	   /** 
     * Translates a Base64 value to either its 6-bit reconstruction value
     * or a negative number indicating some other meaning.
     **/
    private final static byte[] DECODABET =
    {   
        -9,-9,-9,-9,-9,-9,-9,-9,-9,                 // Decimal  0 -  8
        -5,-5,                                      // Whitespace: Tab and Linefeed
        -9,-9,                                      // Decimal 11 - 12
        -5,                                         // Whitespace: Carriage Return
        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 14 - 26
        -9,-9,-9,-9,-9,                             // Decimal 27 - 31
        -5,                                         // Whitespace: Space
        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,              // Decimal 33 - 42
        62,                                         // Plus sign at decimal 43
        -9,-9,-9,                                   // Decimal 44 - 46
        63,                                         // Slash at decimal 47
        52,53,54,55,56,57,58,59,60,61,              // Numbers zero through nine
        -9,-9,-9,                                   // Decimal 58 - 60
        -1,                                         // Equals sign at decimal 61
        -9,-9,-9,                                      // Decimal 62 - 64
        0,1,2,3,4,5,6,7,8,9,10,11,12,13,            // Letters 'A' through 'N'
        14,15,16,17,18,19,20,21,22,23,24,25,        // Letters 'O' through 'Z'
        -9,-9,-9,-9,-9,-9,                          // Decimal 91 - 96
        26,27,28,29,30,31,32,33,34,35,36,37,38,     // Letters 'a' through 'm'
        39,40,41,42,43,44,45,46,47,48,49,50,51,     // Letters 'n' through 'z'
        -9,-9,-9,-9                                 // Decimal 123 - 126
        /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 127 - 139
        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 140 - 152
        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 153 - 165
        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 166 - 178
        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 179 - 191
        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 192 - 204
        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 205 - 217
        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 218 - 230
        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 231 - 243
        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9         // Decimal 244 - 255 */
    };
    
    private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
    
    private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
    
    /** The equals sign (=) as a byte. */
    private final static byte EQUALS_SIGN = (byte)'=';
	
	   private static char[]  map1 = new char[64];
	   static {
	      int  i  = 0;
	      for ( char c = 'A'; c <= 'Z'; c++ ) {
	         map1[i++] = c;
	      }
	      for ( char c = 'a'; c <= 'z'; c++ ) {
	         map1[i++] = c;
	      }
	      for ( char c = '0'; c <= '9'; c++ ) {
	         map1[i++] = c;
	      }
	      map1[i++] = '+';
	      map1[i++] = '/';
	   }
	 
	   public static String base64Encode( byte[] in ) {
	      int     iLen      = in.length;
	      int     oDataLen  = ( iLen * 4 + 2 ) / 3;// output length without padding
	      int     oLen      = ( ( iLen + 2 ) / 3 ) * 4;// output length including padding
	      char[]  out       = new char[oLen];
	      int     ip        = 0;
	      int     op        = 0;
	      int     i0;
	      int     i1;
	      int     i2;
	      int     o0;
	      int     o1;
	      int     o2;
	      int     o3;
	      while ( ip < iLen ) {
	         i0 = in[ip++] & 0xff;
	         i1 = ip < iLen ? in[ip++] & 0xff : 0;
	         i2 = ip < iLen ? in[ip++] & 0xff : 0;
	         o0 = i0 >>> 2;
	         o1 = ( ( i0 & 3 ) << 4 ) | ( i1 >>> 4 );
	         o2 = ( ( i1 & 0xf ) << 2 ) | ( i2 >>> 6 );
	         o3 = i2 & 0x3F;
	         out[op++] = map1[o0];
	         out[op++] = map1[o1];
	         out[op] = op < oDataLen ? map1[o2] : '=';
	         op++;
	         out[op] = op < oDataLen ? map1[o3] : '=';
	         op++;
	      }
	      return new String( out );
	   }
	   
	    /**
	     * Decodes data from Base64Util notation.
	     *
	     * @param s the string to decode
	     * @return the decoded data
	     * @since 1.4
	     */
	    public static byte[] decode( String s )
	    {   
	        byte[] bytes = s.getBytes();
	        return decode( bytes, 0, bytes.length );
	    }   // end decode
	    
	    /**
	     * Decodes Base64Util content in byte array format and returns
	     * the decoded byte array.
	     *
	     * @param source The Base64Util encoded data
	     * @param off    The offset of where to begin decoding
	     * @param len    The length of characters to decode
	     * @return decoded data
	     * @since 1.3
	     */
	    public static byte[] decode( byte[] source, int off, int len )
	    {
	        int    len34   = len * 3 / 4;
	        byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output
	        int    outBuffPosn = 0;
	        
	        byte[] b4        = new byte[4];
	        int    b4Posn    = 0;
	        int    i         = 0;
	        byte   sbiCrop   = 0;
	        byte   sbiDecode = 0;
	        for( i = 0; i < len; i++ )
	        {
	            sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits
	            sbiDecode = DECODABET[ sbiCrop ];
	            
	            if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better
	            {
	                if( sbiDecode >= EQUALS_SIGN_ENC )
	                {
	                    b4[ b4Posn++ ] = sbiCrop;
	                    if( b4Posn > 3 )
	                    {
	                        outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn );
	                        b4Posn = 0;
	                        
	                        // If that was the equals sign, break out of 'for' loop
	                        if( sbiCrop == EQUALS_SIGN )
	                            break;
	                    }   // end if: quartet built
	                    
	                }   // end if: equals sign or better
	                
	            }   // end if: white space, equals sign or better
	            else
	            {
	                System.err.println( "Bad Base64Util input character at " + i + ": " + source[i] + "(decimal)" );
	                return null;
	            }   // end else: 
	        }   // each input character
	                                   
	        byte[] out = new byte[ outBuffPosn ];
	        System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); 
	        return out;
	    }   // end decode
	    
	    /**
	     * Decodes the first four bytes of array <var>fourBytes</var>
	     * and returns an array up to three bytes long with the
	     * decoded values.
	     *
	     * @param fourBytes the array with Base64Util content
	     * @return array with decoded values
	     * @since 1.3
	     */
	    private static byte[] decode4to3( byte[] fourBytes )
	    {
	        byte[] outBuff1 = new byte[3];
	        int    count    = decode4to3( fourBytes, 0, outBuff1, 0 );
	        byte[] outBuff2 = new byte[ count ];
	        
	        for( int i = 0; i < count; i++ )
	            outBuff2[i] = outBuff1[i];
	        
	        return outBuff2;
	    }
	    
	    
	    
	    
	    /**
	     * Decodes four bytes from array <var>source</var>
	     * and writes the resulting bytes (up to three of them)
	     * to <var>destination</var>.
	     * The source and destination arrays can be manipulated
	     * anywhere along their length by specifying 
	     * <var>srcOffset</var> and <var>destOffset</var>.
	     * This method does not check to make sure your arrays
	     * are large enough to accomodate <var>srcOffset</var> + 4 for
	     * the <var>source</var> array or <var>destOffset</var> + 3 for
	     * the <var>destination</var> array.
	     * This method returns the actual number of bytes that 
	     * were converted from the Base64Util encoding.
	     * 
	     *
	     * @param source the array to convert
	     * @param srcOffset the index where conversion begins
	     * @param destination the array to hold the conversion
	     * @param destOffset the index where output will be put
	     * @return the number of decoded bytes converted
	     * @since 1.3
	     */
	    private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset )
	    {
	        // Example: Dk==
	        if( source[ srcOffset + 2] == EQUALS_SIGN )
	        {
	            int outBuff =   ( ( DECODABET[ source[ srcOffset    ] ] << 24 ) >>>  6 )
	                          | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
	            
	            destination[ destOffset ] = (byte)( outBuff >>> 16 );
	            return 1;
	        }
	        
	        // Example: DkL=
	        else if( source[ srcOffset + 3 ] == EQUALS_SIGN )
	        {
	            int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] << 24 ) >>>  6 )
	                          | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
	                          | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
	            
	            destination[ destOffset     ] = (byte)( outBuff >>> 16 );
	            destination[ destOffset + 1 ] = (byte)( outBuff >>>  8 );
	            return 2;
	        }
	        
	        // Example: DkLE
	        else
	        {
	            int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] << 24 ) >>>  6 )
	                          | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
	                          | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
	                          | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );

	            destination[ destOffset     ] = (byte)( outBuff >> 16 );
	            destination[ destOffset + 1 ] = (byte)( outBuff >>  8 );
	            destination[ destOffset + 2 ] = (byte)( outBuff       );
	            return 3;
	        }
	    }   // end decodeToBytes
	   
}
